package com.google.hackathon.geochallenge;

import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DoChallengeActivity extends FragmentActivity implements LocationListener {

    public static final String INTENT_EXTRA_CHALLENGE = "challenge";

    private static final String TAG = DoChallengeActivity.class.getSimpleName();
    private static final String BUNDLE_NAME = TAG + "_INSTANCE_DATA";
    private static final String LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
    private static final float MAX_LOCATION_ACCURACY_METERS = 20.0f;

    // Bundle keys
    private static final String M_CURRENT_CHECKPOINT_INDEX = "mCurrentCheckpointIndex";

    private GoogleMap mMap; // Might be null if Google Play services APK is not available.
    private Challenge mChallenge;
    private int mCurrentCheckpointIndex;
    private Map<Integer, Marker> mCheckpointIndexToMarkerMap;
    private LocationManager mLocationManager;
    private Polyline mIndicatorLine;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_do_challenge_activity);

        Intent intent = getIntent();
        mChallenge = intent.getParcelableExtra(INTENT_EXTRA_CHALLENGE);

        mCurrentCheckpointIndex = 0;

        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        mLocationManager.requestLocationUpdates(LOCATION_PROVIDER, 0, 0, this);

        if (savedInstanceState != null) {
            final Bundle instanceData = (savedInstanceState.containsKey(BUNDLE_NAME) ?
                    savedInstanceState.getBundle(BUNDLE_NAME) : null);
            if (instanceData == null) {
                Log.e(TAG, "savedInstanceState does not contain entry for key=" + BUNDLE_NAME);
            } else {
                mCurrentCheckpointIndex = instanceData.getInt(M_CURRENT_CHECKPOINT_INDEX);
            }
        }

        setUpMapIfNeeded();
    }

    @Override
    protected void onResume() {
        super.onResume();
        setUpMapIfNeeded();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLocationManager.removeUpdates(this);
        Log.i(TAG, "done onDestroy()");
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (outState.containsKey(BUNDLE_NAME)) {
            Log.e(TAG, "outState Bundle already contains entry for key=" + BUNDLE_NAME);
            return;
        }

        Bundle instanceData = new Bundle();
        instanceData.putInt(M_CURRENT_CHECKPOINT_INDEX, mCurrentCheckpointIndex);
        outState.putBundle(BUNDLE_NAME, instanceData);
    }

    /**
     * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly
     * installed) and the map has not already been instantiated.. This will ensure that we only ever
     * call {@link #setUpMap()} once when {@link #mMap} is not null.
     * <p>
     * If it isn't installed {@link SupportMapFragment} (and
     * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to
     * install/update the Google Play services APK on their device.
     * <p>
     * A user can return to this FragmentActivity after following the prompt and correctly
     * installing/updating/enabling the Google Play services. Since the FragmentActivity may not
     * have been completely destroyed during this process (it is likely that it would only be
     * stopped or paused), {@link #onCreate(Bundle)} may not be called again so we should call this
     * method in {@link #onResume()} to guarantee that it will be called.
     */
    private void setUpMapIfNeeded() {
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }

    private void drawIndicator(Location curLocation) {
        Log.i("DELETEME", "in drawIndicator");
        if (mIndicatorLine != null) {
            mIndicatorLine.remove();
        }
        if (mCurrentCheckpointIndex >= mChallenge.getCheckpoints().size()) {
            return;
        }

        Checkpoint checkpoint = mChallenge.getCheckpoints().get(mCurrentCheckpointIndex);

        // line should be max 5m
        final double MAX_INDICATOR_LEN = 15.0;
        float[] results = new float[1];
        double indicatorLat, indicatorLon;
        Location.distanceBetween(curLocation.getLatitude(),curLocation.getLongitude(),
                checkpoint.getLatitude(), checkpoint.getLongitude(), results);
        if (results[0] <= MAX_INDICATOR_LEN) {
            indicatorLat = checkpoint.getLatitude();
            indicatorLon = checkpoint.getLongitude();
        } else {
            double factor = MAX_INDICATOR_LEN/results[0];
            double deltaLat = checkpoint.getLatitude() - curLocation.getLatitude();
            double deltaLon = checkpoint.getLongitude() - curLocation.getLongitude();
            indicatorLat = curLocation.getLatitude() + factor*deltaLat;
            indicatorLon = curLocation.getLongitude() + factor*deltaLon;
        }

        mIndicatorLine = mMap.addPolyline(new PolylineOptions());
        mIndicatorLine.setColor(0xffff0000);
        List<LatLng> curPoints = mIndicatorLine.getPoints();
        curPoints.add(new LatLng(curLocation.getLatitude(), curLocation.getLongitude()));
        curPoints.add(new LatLng(indicatorLat, indicatorLon));
        mIndicatorLine.setPoints(curPoints);
    }
    /**
     * This is where we can add markers or lines, add listeners or move the camera.
     * <p>
     * This should only be called once and when we are sure that {@link #mMap} is not null.
     */
    private void setUpMap() {
        mCheckpointIndexToMarkerMap = new HashMap<Integer, Marker>();

        LatLngBounds.Builder boundsBuilder = LatLngBounds.builder();

        int index = mCurrentCheckpointIndex;
        for (Checkpoint checkpoint : mChallenge.getCheckpoints().subList(mCurrentCheckpointIndex, mChallenge.getCheckpoints().size())) {
            MarkerOptions options = new MarkerOptions()
                    .position(new LatLng(checkpoint.getLatitude(), checkpoint.getLongitude()))
                    .title(String.valueOf(index + 1))
                    .alpha(0.5f);
            mCheckpointIndexToMarkerMap.put(index, mMap.addMarker(options));

            boundsBuilder.include(new LatLng(checkpoint.getLatitude(), checkpoint.getLongitude()));

            index++;
        }

        mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        mMap.setMyLocationEnabled(true);

        if (mCurrentCheckpointIndex < mChallenge.getCheckpoints().size()) {
            Marker currentMarker = mCheckpointIndexToMarkerMap.get(mCurrentCheckpointIndex);
            currentMarker.setAlpha(1.0f);

            final LatLngBounds bounds = boundsBuilder.build();

            mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
                @Override
                public void onCameraChange(CameraPosition cameraPosition) {
                    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 10));
                    mMap.setOnCameraChangeListener(null);
                }
            });
        }
    }

    @Override
    public void onLocationChanged(Location location) {
        if (mCurrentCheckpointIndex >= mChallenge.getCheckpoints().size()) {
            // Ignore location updates if challenge is completed.
            return;
        }

        Checkpoint checkpoint = mChallenge.getCheckpoints().get(mCurrentCheckpointIndex);

        float distanceResults[] = new float[1];
        Location.distanceBetween(checkpoint.getLatitude(), checkpoint.getLongitude(), location.getLatitude(), location.getLongitude(), distanceResults);

        if (distanceResults[0] <= checkpoint.getAccuracy() + Math.min(MAX_LOCATION_ACCURACY_METERS, location.getAccuracy())) {
            Log.i(TAG, "Checkpoint " + mCurrentCheckpointIndex + " reached!");

            Marker marker = mCheckpointIndexToMarkerMap.get(mCurrentCheckpointIndex);
            marker.remove();
            mCheckpointIndexToMarkerMap.remove(mCurrentCheckpointIndex);

            mCurrentCheckpointIndex++;

            if (mCurrentCheckpointIndex >= mChallenge.getCheckpoints().size()) {
                Log.i(TAG, "Challenge completed!");
                Toast.makeText(this, R.string.challenge_completed_message, Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, R.string.checkpoint_reached_message, Toast.LENGTH_SHORT).show();
                Marker currentMarker = mCheckpointIndexToMarkerMap.get(mCurrentCheckpointIndex);
                currentMarker.setAlpha(1.0f);
            }
        }

        drawIndicator(location);
    }

    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {

    }

    @Override
    public void onProviderEnabled(String s) {

    }

    @Override
    public void onProviderDisabled(String s) {

    }
}
